#include "FeedbackScaling.h"
#include "../Operations/OperationManager.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;

const float CIView::FeedbackScaling::CUBE_EDGE = 1.8f;

/// <summary>
/// Constructor
/// </summary>
CIView::FeedbackScaling::FeedbackScaling() : x (false), y (false), z (false) {

    cube = Box(Vertex (-CUBE_EDGE,
                       -CUBE_EDGE,
                       -CUBE_EDGE),                            

               Vertex (CUBE_EDGE,
                       CUBE_EDGE,
                       CUBE_EDGE));


    
    coordinate = Point (CUBE_EDGE*13, CUBE_EDGE*13, CUBE_EDGE*13);

    //Defining the bounding box of the axes (used for axes selection)
    x_axisBoundingBox = Box (Vertex (0, -CUBE_EDGE, -CUBE_EDGE), 
                             Vertex (CUBE_EDGE*14, CUBE_EDGE, CUBE_EDGE));
    
    y_axisBoundingBox = Box (Vertex (-CUBE_EDGE, 0, -CUBE_EDGE), 
                             Vertex (CUBE_EDGE, CUBE_EDGE*14, CUBE_EDGE));
    
    z_axisBoundingBox = Box (Vertex (-CUBE_EDGE, -CUBE_EDGE, 0), 
                             Vertex (CUBE_EDGE, CUBE_EDGE, CUBE_EDGE*14));
}

/// <summary>
/// Destructor
/// </summary>
CIView::FeedbackScaling::~FeedbackScaling() {}






///<************************************************************************************************************>
///<*********************************** region Protected Utility Methods ***************************************>
///<************************************************************************************************************>

/// <summary>
/// Method called to draw the "ghost" feedback while necessary
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackScaling::DrawGhostFeedback (Camera& camera) {
    //Drawing the "ghost" feedback
    glPushMatrix();                 
        //Enabling blending for transparency
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable (GL_BLEND);                                //Enables blending

        glColor4f (1, 1, 1, 0.4f);

        float length = CUBE_EDGE*13;        
        
        glLineWidth (1.0f);

        if (camera.type != Camera::RIGHT && x) {
            //x-axis
            glPushMatrix();
                //Drawing the x-axis
                glBegin (GL_LINES);                                         //The axis
                    glVertex3f (0, 0, 0);
                    glVertex3f (length, 0, 0);
                glEnd();

                glTranslatef (length, 0, 0);
                cube.RenderFilled();                                        //The cube
            glPopMatrix();
        }

        if (camera.type != Camera::TOP && y) {
            //y-axis
            glPushMatrix();
                //Drawing the y-axis
                glBegin (GL_LINES);                                         //The axis
                    glVertex3f (0, 0, 0);
                    glVertex3f (0, length, 0);
                glEnd();

                glTranslatef (0, length, 0);
                cube.RenderFilled();                                        //The cube
            glPopMatrix();
        }

        if (camera.type != Camera::FRONT && z) {
            //z-axis
            glPushMatrix();
                //Drawing the z-axis 
                glBegin (GL_LINES);                                         //The axis
                    glVertex3f (0, 0, 0);
                    glVertex3f (0, 0, length);
                glEnd();

                glTranslatef (0, 0, length);
                cube.RenderFilled();                                        //The cube
            glPopMatrix();
        }

        glDisable (GL_BLEND);                               //Disables blending
    glPopMatrix();
}
/***************************************************************************************************************
************************************** endregion Protected Utility Methods *************************************
****************************************************************************************************************/









///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary>
/// Method called to compute the feedback position
/// </summary>
/// <param name="scene"> A reference to the 3D scene </param>
void CIView::FeedbackScaling::ComputeFeedbackPosition (Scene& scene) {
    map<const char*, Geometry*>& geometries = scene.GetGeometries();
    map<const char*, Geometry*>::iterator it = geometries.begin();
    renderizable = false;
    
    DeselectAll();  //Deselects all feedback axes
    Box box;        //The bounding box of the selected vertices

    //Checking selected vertices
    while (it != geometries.end()) {
        VertexLib& vertices = it->second->GetVertices();
        VertexIter v = vertices.begin();

        if (it->second->hasPrimitivesSelected) {

            while (v != vertices.end()) {
                if ((*v)->selected) {           
                    renderizable = true;        //Since there is a vertex selected, the feedback can be rendered
                    box.Update (*(*v));         //Updates the bounding box of the selected vertices
                }
                v++;
            }
        }

        it++;
    }

    //If the rotation is around the origin
    if (CIOps::OperationManager::Origin)
        position = Point::ORIGIN;               //The feedback center is the origin
    else
        position = box.GetCentre();             //Computes the centre of the bounding box  
}

/// <summary>
/// Method called to draw the scaling feedback
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackScaling::Render (Camera& camera) {
    if( !renderizable)      //If the feedback can't be rendered (for example, because there are no primitives selected)
        return;             //must return

    GLint oldModel;
    glGetIntegerv (GL_SHADE_MODEL, &oldModel);      //Gets the current shade model
    glShadeModel (GL_SMOOTH);                       //Sets the smooth shade model

    
    double scaleValue = 1.0 / camera.zoom;    
        
    glLineWidth (1.0f);

    glPushMatrix();                 
        glTranslatef (position.x, position.y, position.z);
        glScaled (scaleValue, scaleValue, scaleValue);

        //Drawing the "ghost" feedback (only if at least one axis is selected)
        if (x || y || z)
            DrawGhostFeedback (camera);

        
        //glEnable (GL_DEPTH_TEST);       //Enables z-buffering

        //The central box             
        glColor3f (1, 1, 0);
        cube.Render();

        //TODO -> Use materials instead of glColor!

        if (camera.type != Camera::RIGHT) {
            //x-axis
            glPushMatrix();
                x ? glColor3f (1, 1, 0) : glColor3f (1, 0, 0);  //The x-axis color according to its selection status                

                //Drawing the x-axis
                glBegin (GL_LINES);                                         //The axis
                    glVertex3f (0, 0, 0);
                    glVertex3f (coordinate.x, 0, 0);
                glEnd();

                glTranslatef (coordinate.x, 0, 0);
                cube.RenderFilled();                                        //The cube
            glPopMatrix();
        }

        if (camera.type != Camera::TOP) {
            //y-axis
            glPushMatrix();
                y ? glColor3f (1, 1, 0) : glColor3f (0, 1, 0);  //The y-axis color according to its selection status
                
                //Drawing the y-axis
                glBegin (GL_LINES);                                         //The axis
                    glVertex3f (0, 0, 0);
                    glVertex3f (0, coordinate.y, 0);
                glEnd();

                glTranslatef (0, coordinate.y, 0);
                cube.RenderFilled();                                        //The cube
            glPopMatrix();
        }

        if (camera.type != Camera::FRONT) {
            //z-axis
            glPushMatrix();
                z ? glColor3f (1, 1, 0) : glColor3f (0, 0, 1);  //The z-axis color according to its selection status
                
                //Drawing the z-axis 
                glBegin (GL_LINES);                                         //The axis
                    glVertex3f (0, 0, 0);
                    glVertex3f (0, 0, coordinate.z);
                glEnd();

                glTranslatef (0, 0, coordinate.z);
                cube.RenderFilled();                                        //The cube
            glPopMatrix();
        }
        
        //glDisable (GL_DEPTH_TEST);      //Disables z-buffering

    glPopMatrix();

    glShadeModel (oldModel);                //Restores the old shade model

}

/// <summary>
/// Method called to load the names for the axes in the OpenGL names stack       
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackScaling::LoadAxesNames (Camera& camera) {
    if( !renderizable)      //If the feedback can't be rendered (for example, because there are no primitives selected)
        return;             //must return

    double scaleValue = 1.0 / camera.zoom;

    glPushMatrix();                 
        glTranslatef (position.x, position.y, position.z);
        glScaled (scaleValue, scaleValue, scaleValue);

        glLoadName (ALL_AXES);
        cube.RenderFilled();

        if (camera.type != Camera::RIGHT) {
            //x-axis
            glLoadName (X_AXIS);                            //Loads the name for the x-axis
            x_axisBoundingBox.RenderFilled();            
        }

        if (camera.type != Camera::TOP) {
            //y-axis           
            glLoadName (Y_AXIS);                            //Loads the name for the y-axis
            y_axisBoundingBox.RenderFilled();            
        }

        if (camera.type != Camera::FRONT) {
            //z-axis            
            glLoadName (Z_AXIS);                            //Loads the name for the z-axis
            z_axisBoundingBox.RenderFilled();            
        }
    glPopMatrix();
}

/// <summary>
/// Method called to set the x-axis as selected
/// </summary>
void CIView::FeedbackScaling::SelectX() {
    x = true;
    y = false;
    z = false;
}

/// <summary>
/// Method called to set the y-axis as selected
/// </summary>
void CIView::FeedbackScaling::SelectY() {
    x = false;
    y = true;
    z = false;
}

/// <summary>
/// Method called to set the z-axis as selected
/// </summary>
void CIView::FeedbackScaling::SelectZ() {
    x = false;
    y = false;
    z = true;
}

/// <summary>
/// Method called to set all axes as selected
/// </summary>
void CIView::FeedbackScaling::SelectAll() {
    x = true;
    y = true;
    z = true;
}

/// <summary>
/// Method called to set all axes as deselected
/// </summary>
void CIView::FeedbackScaling::DeselectAll() {
    x = false;
    y = false;
    z = false;
}

/// <summary>
/// Method called to know if the x-axis is selected
/// </summary>
/// <returns> A boolean that indicates if the x-axis is selected </returns>
bool CIView::FeedbackScaling::IsSelectedX() {
    return x;
}

/// <summary>
/// Method called to know if the y-axis is selected
/// </summary>
/// <returns> A boolean that indicates if the y-axis is selected </returns>
bool CIView::FeedbackScaling::IsSelectedY() {
    return y;
}

/// <summary>
/// Method called to know if the z-axis is selected
/// </summary>
/// <returns> A boolean that indicates if the z-axis is selected </returns>
bool CIView::FeedbackScaling::IsSelectedZ() {
    return z;
}

/// <summary>
/// Method called to apply a matrix transformation to the feedback axes coordinates
/// </summary>
/// <param name="m"> The matrix to apply </param>
void CIView::FeedbackScaling::ApplyMatrix (const Matrix& m) {
    coordinate.Transform (m);
}

/// <summary>
/// Method called to reset the feedback axes coordinates
/// </summary>
void CIView::FeedbackScaling::Reset() {
    coordinate.x = CUBE_EDGE*13;
    coordinate.y = CUBE_EDGE*13;
    coordinate.z = CUBE_EDGE*13;
}

/// <summary>
/// Method called to get the feedback position
/// </summary>
/// <returns> The feedback position </returns>
Point& CIView::FeedbackScaling::GetPosition() {
    return position;
}
/***************************************************************************************************************
******************************************* endregion Utility Methods ******************************************
****************************************************************************************************************/
